
#include "ly/shapes.hpp"
#include <spdlog/spdlog.h>
#include "ly/array.hpp"
#include "ly/manager.hpp"
#include "ly/properties.hpp"
#include "ly/trans.hpp"

namespace ly {
/**
 *  @brief A base class for instance operations 
 *
 *  This class is used for the Op classes for the undo/redo queuing mechanism.
 */
class ShapeOpBase : public Op {
 public:
  ShapeOpBase() : Op() {}

  virtual void undo(Shapes* Shapes) = 0;
  virtual void redo(Shapes* Shapes) = 0;
};

// Template operation for component insertion/removal
template <typename Component>
class ComponentOp : public ShapeOpBase {

 public:
  typedef std::tuple<CplxTrans, Trans, ObjectWithProperties, RectangularArray, RegularArray> component_types;
  ComponentOp(bool is_insert, entt::entity e, const Component* comp_data = nullptr) : m_insert(is_insert), m_e(e) {
    if (comp_data) {
      m_data = *comp_data;
    }
  }

  void undo(Shapes* insts) override {
    if (m_insert) {
      remove(insts);
    } else {
      insert(insts);
    }
  }

  void redo(Shapes* insts) override {
    if (m_insert) {
      insert(insts);
    } else {
      remove(insts);
    }
  }

 private:
  void insert(Shapes* insts) {
    auto& reg = insts->reg();
    if constexpr (std::is_same_v<Component, Polygon>) {
      reg.emplace_or_replace<Polygon>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, Trans>) {
      reg.emplace_or_replace<Trans>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, CplxTrans>) {
      reg.emplace_or_replace<CplxTrans>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, ObjectWithProperties>) {
      reg.emplace_or_replace<ObjectWithProperties>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, RectangularArray>) {
      reg.emplace_or_replace<RectangularArray>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, RegularArray>) {
      reg.emplace_or_replace<RegularArray>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, ExplicitArray>) {
      reg.emplace_or_replace<ExplicitArray>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, ExplicitXArray>) {
      reg.emplace_or_replace<ExplicitXArray>(m_e, m_data);
    } else if constexpr (std::is_same_v<Component, ExplicitYArray>) {
      reg.emplace_or_replace<ExplicitYArray>(m_e, m_data);
    }
  }

  void remove(Shapes* insts) {
    auto& reg = insts->reg();
    reg.remove<Component>(m_e);
  }

  bool m_insert;
  entt::entity m_e;
  Component m_data;
};

void Shapes::emplace_or_replace(entt::entity e, const Polygon& com) {
  emplace(e, com);
};
void Shapes::emplace_or_replace(entt::entity e, const Path& com) {
  emplace(e, com);
};
void Shapes::emplace_or_replace(entt::entity e, const ObjectWithProperties& com) {
  emplace(e, com);
};

void Shapes::undo(Op* op) {
  ShapeOpBase* instop = dynamic_cast<ShapeOpBase*>(op);
  if (instop) {
    instop->undo(this);
  }
}

void Shapes::redo(Op* op) {
  //  actions are only queued by the instance list - this is should be
  //  responsible for the handling of the latter.
  //  HACK: this is not really a nice concept, but it saves us a pointer to the manager.
  ShapeOpBase* instop = dynamic_cast<ShapeOpBase*>(op);
  if (instop) {
    instop->redo(this);
  }
}

void Shapes::print_all() {
  auto print_coms = [this](entt::entity e) {
    auto& m_reg = this->m_reg;
    std::string com_str = "";
    if (m_reg.try_get<Trans>(e)) {
      com_str += "[Trans] ";
    }
    if (m_reg.try_get<CplxTrans>(e)) {
      com_str += "[CplxTrans] ";
      auto& ct = m_reg.get<CplxTrans>(e);
      ct.to_mat2();
    }
    if (m_reg.try_get<RegularArray>(e)) {
      com_str += "[RegularArray] ";
    }
    if (m_reg.try_get<ObjectWithProperties>(e)) {
      com_str += "[ObjectWithProperties] ";
    }
    SPDLOG_INFO("coms: {}", com_str);
  };

  for (auto [e, shape] : m_reg.view<Polygon>().each()) {
    SPDLOG_INFO("polygon entity: {} ", size_t(e));
    print_coms(e);
  }

  for (auto [e, shape] : m_reg.view<Path>().each()) {
    SPDLOG_INFO("path entity: {}", size_t(e));
    print_coms(e);
  }
}

template <typename T>
void Shapes::emplace(entt::entity e, const T& comp) {
  m_reg.emplace<T>(e, comp);
  if (cell()) {
    // // bool editable = is_editable();
    // if (cell()->manager() && cell()->manager()->transacting()) {
    //   cell()->manager()->queue(cell().get(), new ComponentOp<T>(true, e, &comp));
    // }
  }
};

}  // namespace ly
